package com.sqtracker.recommender.service;

import com.sqtracker.recommender.algorithm.ContentBasedRecommender;
import com.sqtracker.recommender.algorithm.ExplorationRecommender;
import com.sqtracker.recommender.algorithm.HybridRecommender;
import com.sqtracker.recommender.algorithm.ItemCFRecommender;
import com.sqtracker.recommender.dto.RecommendationRequest;
import com.sqtracker.recommender.dto.RecommendationResponse;
import com.sqtracker.recommender.model.RecommendationLog;
import com.sqtracker.recommender.model.UserPreference;
import com.sqtracker.recommender.repository.RecommendationLogRepository;
import com.sqtracker.recommender.repository.TorrentMetadataExtRepository;
import com.sqtracker.recommender.repository.UserPreferenceRepository;
import com.sqtracker.recommender.strategy.ColdStartStrategy;
import com.sqtracker.recommender.strategy.MMRDiversityStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class RecommendationService {

    private static final Logger logger = LoggerFactory.getLogger(RecommendationService.class);

    private final ItemCFRecommender itemCFRecommender;
    private final ContentBasedRecommender contentBasedRecommender;
    private final ExplorationRecommender explorationRecommender;
    private final HybridRecommender hybridRecommender;
    private final MMRDiversityStrategy diversityStrategy;
    private final ColdStartStrategy coldStartStrategy;
    private final UserPreferenceRepository userPreferenceRepository;
    private final RecommendationLogRepository recommendationLogRepository;
    private final UserBehaviorService userBehaviorService;
    private final TorrentMetadataExtRepository torrentMetadataExtRepository; // For HybridRecommender

    @Value("${recommendation.weights.item-cf:0.4}")
    private double weightItemCF;

    @Value("${recommendation.weights.content-based:0.5}")
    private double weightContentBased;

    @Value("${recommendation.weights.exploration:0.5}")
    private double weightExploration;

    @Value("${recommendation.cold-start.threshold:5}")
    private int coldStartThreshold;

    @Value("${recommendation.explicit.preference.boost:1.5}") // Configurable boost factor
    private double explicitPreferenceBoostFactor;

    @Autowired
    public RecommendationService(
            ItemCFRecommender itemCFRecommender,
            ContentBasedRecommender contentBasedRecommender,
            ExplorationRecommender explorationRecommender,
            MMRDiversityStrategy diversityStrategy,
            ColdStartStrategy coldStartStrategy,
            UserPreferenceRepository userPreferenceRepository,
            RecommendationLogRepository recommendationLogRepository,
            UserBehaviorService userBehaviorService,
            TorrentMetadataExtRepository torrentMetadataExtRepository) { // Autowire TorrentMetadataExtRepository

        this.itemCFRecommender = itemCFRecommender;
        this.contentBasedRecommender = contentBasedRecommender;
        this.explorationRecommender = explorationRecommender;
        this.diversityStrategy = diversityStrategy;
        this.coldStartStrategy = coldStartStrategy;
        this.userPreferenceRepository = userPreferenceRepository;
        this.recommendationLogRepository = recommendationLogRepository;
        this.userBehaviorService = userBehaviorService;
        this.torrentMetadataExtRepository = torrentMetadataExtRepository; // Store it

        // Initialize hybrid recommender weights
        Map<String, Double> weights = new HashMap<>();
        weights.put("item-cf", weightItemCF);
        weights.put("content-based", weightContentBased);
        weights.put("exploration", weightExploration);
        logger.info("Initializing HybridRecommender with weights: ItemCF={}, ContentBased={}, Exploration={}",
                weightItemCF, weightContentBased, weightExploration);
        logger.info("Explicit preference boost factor: {}", explicitPreferenceBoostFactor);

        this.hybridRecommender = new HybridRecommender(weights, this.torrentMetadataExtRepository, this.explicitPreferenceBoostFactor);
    }

    /**
     * 获取个性化推荐
     */
    public RecommendationResponse getRecommendations(RecommendationRequest request) {
        String userId = request.getUserId();
        int requestedCount = request.getCount();
        logger.info("Received recommendation request for userId: {}, count: {}, page: {}, context: {}",
                userId, requestedCount, request.getPage(), request.getContextPage());

        long interactionCount = userBehaviorService.getUserInteractionCount(userId);
        logger.debug("User {} interaction count: {}", userId, interactionCount);

        // 冷启动处理
        if (interactionCount < coldStartThreshold) {
            logger.info("User {} is in cold start (interactions {} < threshold {}). Using ColdStartStrategy.",
                    userId, interactionCount, coldStartThreshold);
            RecommendationResponse coldStartResponse = coldStartStrategy.getRecommendations(request);
            // Ensure the response doesn't exceed requested count
            limitResponseToRequestedCount(coldStartResponse, requestedCount);
            logRecommendations(userId, coldStartResponse, request.getContextPage() != null ? request.getContextPage() : "cold-start");
            logger.info("Cold start recommendations for user {}: {} items.", userId, coldStartResponse.getRecommendations().size());
            return coldStartResponse;
        }

        // 获取用户偏好
        Optional<UserPreference> userPreference = userPreferenceRepository.findByUserId(userId);
        if (userPreference.isPresent()) {
            logger.debug("User preferences found for userId: {}", userId);
        } else {
            logger.warn("No user preferences found for userId: {}", userId);
        }

        // 获取各个算法的推荐
        RecommendationResponse itemCFResponse = itemCFRecommender.recommend(request);
        logger.info("User {} - ItemCF recommendations: {} items.", userId, itemCFResponse.getRecommendations().size());

        RecommendationResponse contentBasedResponse = contentBasedRecommender.recommend(request);
        logger.info("User {} - ContentBased recommendations: {} items.", userId, contentBasedResponse.getRecommendations().size());

        RecommendationResponse explorationResponse = explorationRecommender.recommend(request);
        logger.info("User {} - Exploration recommendations: {} items.", userId, explorationResponse.getRecommendations().size());

        // 混合推荐
        logger.debug("User {} - Starting hybrid recommendation process.", userId);
        RecommendationResponse hybridResponse = hybridRecommender.recommend(
                itemCFResponse,
                contentBasedResponse,
                explorationResponse,
                userPreference, // Pass Optional<UserPreference>
                userId
        );
        logger.info("User {} - Hybrid recommendations before diversity: {} items.", userId, hybridResponse.getRecommendations().size());
        if (hybridResponse.getRecommendations().isEmpty() && (itemCFResponse.getRecommendations().isEmpty() && contentBasedResponse.getRecommendations().isEmpty() && explorationResponse.getRecommendations().isEmpty())) {
            logger.warn("User {} - All individual recommenders returned empty lists. Hybrid result will also be empty before diversity.", userId);
        }

        // 应用多样性策略
        RecommendationResponse diversifiedResponse = diversityStrategy.diversify(hybridResponse);
        logger.info("User {} - Recommendations after diversity: {} items.", userId, diversifiedResponse.getRecommendations().size());

        // 确保推荐数量不超过请求的数量
        limitResponseToRequestedCount(diversifiedResponse, requestedCount);
        logger.info("User {} - Recommendations after limiting to requested count: {} items.", userId, diversifiedResponse.getRecommendations().size());

        // 记录推荐日志
        logRecommendations(userId, diversifiedResponse, request.getContextPage());
        logger.info("User {} - Final recommendations generated: {} items. RequestId: {}", userId, diversifiedResponse.getRecommendations().size(), diversifiedResponse.getRequestId());

        if (diversifiedResponse.getRecommendations().isEmpty()) {
            logger.warn("User {} -最终返回的推荐列表为空!", userId);
        }

        return diversifiedResponse;
    }

    /**
     * 限制推荐数量不超过请求的数量
     */
    private void limitResponseToRequestedCount(RecommendationResponse response, int requestedCount) {
        if (response == null || response.getRecommendations() == null) {
            logger.warn("Cannot limit recommendations: response or recommendations list is null");
            return;
        }

        List<Map<String, Object>> recommendations = response.getRecommendations();
        if (recommendations.size() > requestedCount) {
            logger.debug("Limiting recommendations from {} to requested count {}", recommendations.size(), requestedCount);
            response.setRecommendations(recommendations.subList(0, requestedCount));
        }
    }

    /**
     * 获取相似资源推荐
     */
    public RecommendationResponse getSimilarRecommendations(RecommendationRequest request) {
        logger.info("Received similar recommendations request for torrentId: {}, userId: {}, count: {}",
                request.getTorrentId(), request.getUserId(), request.getCount());
        // 主要使用内容推荐算法
        RecommendationResponse response = contentBasedRecommender.recommendSimilar(request);

        // 确保推荐数量不超过请求的数量
        limitResponseToRequestedCount(response, request.getCount());
        logger.info("Similar recommendations for torrentId {}: {} items.", request.getTorrentId(), response.getRecommendations().size());

        // 记录日志
        if (request.getUserId() != null) {
            logRecommendations(request.getUserId(), response, "similar");
        }
        return response;
    }

    /**
     * 获取热门推荐
     */
    public RecommendationResponse getTrendingRecommendations(RecommendationRequest request) {
        logger.info("Received trending recommendations request, count: {}, categoryId: {}",
                request.getCount(), request.getCategoryId());
        RecommendationResponse response = explorationRecommender.recommendTrending(request);

        // 确保推荐数量不超过请求的数量
        limitResponseToRequestedCount(response, request.getCount());
        logger.info("Trending recommendations: {} items.", response.getRecommendations().size());
        return response;
    }

    /**
     * 获取推荐统计信息
     */
    public Map<String, Object> getRecommendationStats(String userId) {
        logger.debug("Fetching recommendation stats for userId: {}", userId);
        Map<String, Object> stats = new HashMap<>();
        stats.put("interactionCount", userBehaviorService.getUserInteractionCount(userId));
        stats.put("recommendationCount", recommendationLogRepository.countByUserId(userId));
        stats.put("clickThroughRate", calculateClickThroughRate(userId));
        logger.info("Stats for userId {}: {}", userId, stats);
        return stats;
    }

    /**
     * 记录推荐反馈
     */
    public void recordFeedback(String userId, String recommendationId, String clickedTorrentId, String[] ignoredTorrentIds) {
        logger.info("Recording feedback for userId: {}, recommendationId: {}, clicked: {}, ignoredCount: {}",
                userId, recommendationId, clickedTorrentId, ignoredTorrentIds != null ? ignoredTorrentIds.length : 0);
        Optional<RecommendationLog> logOpt = recommendationLogRepository.findById(recommendationId);

        if (logOpt.isPresent() && logOpt.get().getUserId().equals(userId)) {
            RecommendationLog log = logOpt.get();
            RecommendationLog.RecommendationFeedback feedback = log.getFeedback();
            if (feedback == null) { // Ensure feedback object exists
                feedback = new RecommendationLog.RecommendationFeedback();
                log.setFeedback(feedback);
            }


            if (clickedTorrentId != null && !clickedTorrentId.isEmpty()) {
                if (feedback.getClicked() == null) {
                    feedback.setClicked(new ArrayList<>());
                }
                feedback.getClicked().add(clickedTorrentId);
                logger.debug("Added clickedTorrentId: {} to feedback for logId: {}", clickedTorrentId, recommendationId);
            }

            if (ignoredTorrentIds != null && ignoredTorrentIds.length > 0) {
                if (feedback.getIgnored() == null) {
                    feedback.setIgnored(new ArrayList<>());
                }
                feedback.getIgnored().addAll(Arrays.asList(ignoredTorrentIds));
                logger.debug("Added {} ignoredTorrentIds to feedback for logId: {}", ignoredTorrentIds.length, recommendationId);
            }

            feedback.setTimestamp(new Date());
            recommendationLogRepository.save(log);
            logger.info("Feedback saved for logId: {}", recommendationId);
        } else {
            logger.warn("Could not record feedback. Log not found for recommendationId: {} or userId mismatch (expected: {}, actual: {}).",
                    recommendationId, userId, logOpt.map(RecommendationLog::getUserId).orElse("N/A"));
        }
    }

    /**
     * 计算点击率
     */
    private double calculateClickThroughRate(String userId) {
        long clickCount = userBehaviorService.getUserClickCount(userId);
        long recommendCount = recommendationLogRepository.countByUserId(userId);
        logger.debug("Calculating CTR for userId {}: clicks={}, recommendations={}", userId, clickCount, recommendCount);

        if (recommendCount == 0) {
            return 0.0;
        }
        return (double) clickCount / recommendCount;
    }

    /**
     * 记录推荐日志
     */
    private void logRecommendations(String userId, RecommendationResponse response, String page) {
        if (response == null || response.getRecommendations() == null) {
            logger.warn("Skipping logging for userId {} on page {} due to null response or recommendations.", userId, page);
            return;
        }
        RecommendationLog log = new RecommendationLog();
        log.setUserId(userId);
        log.setRecommendations(response.getRecommendations());
        if (log.getContext() == null) { // Ensure context object exists
            log.setContext(new RecommendationLog.RecommendationContext());
        }
        log.getContext().setTimestamp(new Date());
        log.getContext().setPage(page != null ? page : "unknown");

        try {
            RecommendationLog savedLog = recommendationLogRepository.save(log);
            response.setRequestId(savedLog.getId()); // Set requestId on the original response object
            logger.debug("Recommendations logged for userId: {} on page: {}, count: {}, logId: {}",
                    userId, page, response.getRecommendations().size(), savedLog.getId());
        } catch (Exception e) {
            logger.error("Error saving recommendation log for userId: {}", userId, e);
        }
    }
}